//
// Created by Administrator on 2019/6/6 0006.
//

#include "FFMPegControl.h"
#include "macro.h"

void *prepareThread(void *args) {
    FFMPegControl *ffmPegControl = static_cast<FFMPegControl *>(args);
    ffmPegControl->prepareFFMPeg();
    //线程一定一定一定要返回0
    return 0;
}

//FFMPegControl::FFMPegControl(const char *dataSource) {
//    this->dataSource = new char[strlen(dataSource) + 1];
//    strcpy(this->dataSource, dataSource);
//}

/**
 *  FFMPegControl类构造函数
 *
 *  这里的dataSource是从java传过来的字符串，
 *  通过jni接口转成了C++字符串。
 *  在jni方法中被释放掉了，导致dataSource变成悬空指针的问题（指向一块已经释放了的内存）
 *  如何解决这个问题：用到内存拷贝 strcpy()函数
 *
 * @param javaCallback 接口回调，将数据返回给Java层
 * @param dataSource 视频地址
 */
FFMPegControl::FFMPegControl(JavaCallback *javaCallback, const char *dataSource) {
    this->dataSource = new char[strlen(dataSource) + 1];
    this->javaCallback = javaCallback;
    strcpy(this->dataSource, dataSource);
    duration = 0;
    pthread_mutex_init(&seekMutex, 0);
}

FFMPegControl::~FFMPegControl() {
    pthread_mutex_destroy(&seekMutex);
//    javaCallback = 0;
    DELETE(javaCallback);
    DELETE(dataSource);
}

void FFMPegControl::prepare() {

    //开辟线程
    //todo 参数一：线程id
    //todo 参数二：
    //todo 参数三：线程（具体执行对应的函数）
    //todo 参数四：可以理解为参数三函数中的参数
    pthread_create(&pid_prepare, NULL, prepareThread, this);
}

void FFMPegControl::prepareFFMPeg() {
    //在子线程中执行，能访问到对象的属性
    avformat_network_init();
    //todo 0.5步，初始化上下文
    formatContext = avformat_alloc_context();
    AVDictionary *opts = NULL;
    //设置超时时间为3秒，单位是微秒
    av_dict_set(&opts, "timeout", "3000000", 0);
    //0成功，非0失败
    //todo 第一步，打开媒体
    int ret = avformat_open_input(&formatContext, dataSource, NULL, &opts);
    //释放
    av_dict_free(&opts);
    if (ret != 0) {
        //视频打开失败，反射给Java层
        LOGE("视频打开失败");
        javaCallback->onError(THREAD_CHILD, FFMPEG_CAN_NOT_OPEN_URL);
        return;
    }
    //todo 第二部，查找媒体中的流信息
    if (avformat_find_stream_info(formatContext, NULL) < 0) {
        if (javaCallback) {
            LOGE("找不到流媒体");
            javaCallback->onError(THREAD_CHILD, FFMPEG_CAN_NOT_FIND_STREAMS);
        }
        return;
    }
    duration = formatContext->duration / 1000000;
    for (int i = 0; i < formatContext->nb_streams; ++i) {
        //todo 第三步，获取媒体流（视频或者视频）
        AVStream *stream=formatContext->streams[i];
        //todo 第四步，获取编解码这段流的参数
        AVCodecParameters *codecpar = stream->codecpar;
        //todo 第五步，通过参数中的id（编解码方式），来查找当前流的解码器
        AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
        if (!dec) {
            if (javaCallback) {
                LOGE("找不到解码器");
                javaCallback->onError(THREAD_CHILD, FFMPEG_FIND_DECODER_FAIL);
            }
            return;
        }
        //todo 第六步，创建解码器上下文
        AVCodecContext *codecContext = avcodec_alloc_context3(dec);
        if (!codecContext) {
            if (javaCallback) {
                LOGE("无法根据解码器创建上下文");
                javaCallback->onError(THREAD_CHILD, FFMPEG_ALLOC_CODEC_CONTEXT_FAIL);
            }
            return;
        }
        //todo 第七步，设置解码器上下文参数
        if (avcodec_parameters_to_context(codecContext, codecpar) < 0) {
            if (javaCallback) {
                LOGE("根据流信息 配置上下文参数失败");
                javaCallback->onError(THREAD_CHILD, FFMPEG_CODEC_CONTEXT_PARAMETERS_FAIL);
            }
            return;
        }
        //todo 第八步，打开解码器
        if (avcodec_open2(codecContext, dec, 0) != 0) {
            if (javaCallback) {
                LOGE("打开解码器失败");
                javaCallback->onError(THREAD_CHILD, FFMPEG_OPEN_DECODER_FAIL);
            }
            return;
        }

        //判断流类型（音频还是视频）
        if (codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            //音频处理
            audioChannel = new AudioChannel(i, javaCallback, codecContext, stream->time_base);

        } else if (codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            //视频处理
            AVRational frame_rate=  stream->avg_frame_rate;
            int fps =av_q2d(frame_rate);
            videoChannel = new VideoChannel(i, javaCallback, codecContext, stream->time_base);
            videoChannel->setRenderCallback(renderFrame);
            videoChannel->setFps(fps);
        }
    }

    if (!audioChannel && !videoChannel) {
        if (javaCallback) {
            LOGE("没有音视频");
            javaCallback->onError(THREAD_CHILD, FFMPEG_NOMEDIA);
        }
        return;
    }
    videoChannel->audioChannel = audioChannel;
    if (javaCallback) {
        javaCallback->onPrepare(THREAD_CHILD);
    }
}

void *startThread(void *args) {
    FFMPegControl *ffmpeg = static_cast<FFMPegControl *>(args);
    ffmpeg->play();
    return 0;
}
void FFMPegControl::start() {
    isPlaying = true;
    if (audioChannel) {
        audioChannel->play();
    }
    if (videoChannel) {
        videoChannel->play();
    }
    pthread_create(&pid_play, NULL, startThread, this);
}

void FFMPegControl::play() {
    int ret = 0;
    while (isPlaying) {
        if (audioChannel && audioChannel->pkt_queue.size() > 100) {
            // 防止程序奔溃，
            // 因为生产速度远远大于消费速度，这个时候会导致pkt_queue的长度过大导致系统奔溃
            av_usleep(10 * 1000);
            continue;
        }

        if (videoChannel && videoChannel->pkt_queue.size() > 100) {
            av_usleep(1000 * 10);
            continue;
        }

        //读取包
        AVPacket *packet = av_packet_alloc();
        //从媒体中读取音频，视频包
        ret = av_read_frame(formatContext, packet);
        if (ret == 0) {
            //将数据包导入队列
            if (audioChannel && packet->stream_index == audioChannel->channelId) {
                audioChannel->pkt_queue.enQueue(packet);
            } else if (videoChannel && packet->stream_index == videoChannel->channelId) {
                videoChannel->pkt_queue.enQueue(packet);
            }
        } else if (ret == AVERROR_EOF) {
            //读取完毕 但是不一定播放完毕
            if (videoChannel->pkt_queue.empty() && videoChannel->frame_queue.empty() &&
            audioChannel->pkt_queue.empty() && audioChannel->frame_queue.empty()) {
                break;
            }
            //因为seek的存在，就算读取完毕，依然要循环去执行av_read_frame();否则seek了没用
        } else {
            break;
        }
    }
    isPlaying = 0;
    videoChannel->stop();
    audioChannel->stop();
}

void FFMPegControl::setRenderCallback(RenderFrame renderFrame) {
    this->renderFrame = renderFrame;
}

jint FFMPegControl::getDuration() {
    return duration;
}

void FFMPegControl::seek(jint progress) {
    if (progress <= 0 || progress >= duration) {
        return;
    }
    if (!formatContext) {
        return;
    }
    pthread_mutex_lock(&seekMutex);
    isSeek = 1;
    int64_t seek = progress * 1000000;
    av_seek_frame(formatContext, -1, seek, AVSEEK_FLAG_BACKWARD);
    //清空队列
    if (audioChannel) {
        audioChannel->stopWork();
        audioChannel->clear();
        audioChannel->startWork();
    }
    if (videoChannel) {
        videoChannel->stopWork();
        videoChannel->clear();
        videoChannel->startWork();
    }
    pthread_mutex_unlock(&seekMutex);
    isSeek = 0;
}

void *async_stop(void *args) {
    FFMPegControl *ffmPegControl = static_cast<FFMPegControl *>(args);
    pthread_join(ffmPegControl->pid_prepare, 0);
    ffmPegControl->isPlaying = 0;
    pthread_join(ffmPegControl->pid_play, 0);
    DELETE(ffmPegControl->audioChannel);
    DELETE(ffmPegControl->videoChannel);
    if (ffmPegControl->formatContext) {
        avformat_close_input(&ffmPegControl->formatContext);
        avformat_free_context(ffmPegControl->formatContext);
        ffmPegControl->formatContext = NULL;
    }
    DELETE(ffmPegControl);
    return 0;
}

void FFMPegControl::stop() {
    javaCallback = 0;
    if (audioChannel) {
        audioChannel->javaCallback = 0;
    }
    if (videoChannel) {
        videoChannel->javaCallback = 0;
    }
    isPlaying = 0;
    pthread_create(&pid_stop, 0, async_stop, this);
}
